/* * Sun Public License Notice * * The contents of this file are subject to the Sun Public License * Version 1.0 (the "License"). You may not use this file except in * compliance with the License. A copy of the License is available at * http://www.sun.com/ * * The Original Code is Forte for Java, Community Edition. The Initial * Developer of the Original Code is Sun Microsystems, Inc. Portions * Copyright 1997-2000 Sun Microsystems, Inc. All Rights Reserved. */ package org.openide.modules; import java.beans.Beans; import java.io.*; import java.net.URL; import java.net.MalformedURLException; import java.text.MessageFormat; import java.util.*; import java.util.jar.Manifest; import java.util.jar.Attributes; import org.openide.TopManager; import org.openide.NotifyDescriptor; import org.openide.util.NbBundle; // Informations extracted from the manifest then instructs the IDE to // install actions, loaders, filesystem, etc. /** A description of a module that is constructed from the module's manifest file. * This provides a convenient way to parse a manifest file. * @author Jaroslav Tulach, Ian Formanek, Jesse Glick */ public final class ModuleDescription extends Object { private static final boolean VERBOSE = Boolean.getBoolean ("org.openide.modules.ModuleDescription.VERBOSE"); // NOI18N // ----------------------------------------------------------------------------- // Global tags /** Global tag for whole module. Identifies the JAR as a module and gives its code name. */ public static final Attributes.Name TAG_MAGIC = new Attributes.Name ("OpenIDE-Module"); // NOI18N /** Display name of module. May be localized, e.g. <code>OpenIDE-Module-Name_cs</code>. */ public static final Attributes.Name TAG_NAME = new Attributes.Name ("OpenIDE-Module-Name"); // NOI18N private static final Comparator codeNameComparator = new Comparator () { public int compare (Object o1, Object o2) { ModuleDescription md1 = (ModuleDescription) o1; ModuleDescription md2 = (ModuleDescription) o2; return md1.getCodeName ().compareTo (md2.getCodeName ()); } }; // ----------------------------------------------------------------------------- // Versioning tags /** Specification version tag for whole module. Identifies the specification version of the module. */ public static final Attributes.Name TAG_SPEC_VERSION = new Attributes.Name ("OpenIDE-Module-Specification-Version"); // NOI18N /** Implementation version tag for whole module. Identifies the implementation version of the module. */ public static final Attributes.Name TAG_IMPL_VERSION = new Attributes.Name ("OpenIDE-Module-Implementation-Version"); // NOI18N // ----------------------------------------------------------------------------- // Dependency tags /** Module dependency tag for whole module. Identifies the modules on which this module depends. */ public static final Attributes.Name TAG_MODULE_DEPENDENCIES = new Attributes.Name ("OpenIDE-Module-Module-Dependencies"); // NOI18N /** Package dependency tag for whole module. Identifies the package versions on which this module depends. */ public static final Attributes.Name TAG_PACKAGE_DEPENDENCIES = new Attributes.Name ("OpenIDE-Module-Package-Dependencies"); // NOI18N /** Java dependency tag for whole module. Identifies the Java version on which this module depends. */ public static final Attributes.Name TAG_JAVA_DEPENDENCIES = new Attributes.Name ("OpenIDE-Module-Java-Dependencies"); // NOI18N /** IDE dependency tag for whole module. Identifies the version of the core IDE on which this module depends. */ public static final Attributes.Name TAG_IDE_DEPENDENCIES = new Attributes.Name ("OpenIDE-Module-IDE-Dependencies"); // NOI18N // ----------------------------------------------------------------------------- // ModuleInstall tags /** Name of (optional) main class. Must be public with a no-argument public constructor, and * implement {@link ModuleInstall}. */ public static final Attributes.Name TAG_MAIN = new Attributes.Name ("OpenIDE-Module-Install"); // NOI18N // ----------------------------------------------------------------------------- // Module content tags /** Name of an (optional) JavaHelp-style help set. * May be localized. * <p>For example, the value <code>org.netbeans.module.Index</code> will look for one of the following, according to locale: * <p><code><PRE> * /org/netbeans/module/Index_cs.html * /org/netbeans/module/Index.html * </PRE></code> */ public static final Attributes.Name TAG_DESCRIPTION = new Attributes.Name ("OpenIDE-Module-Description"); // NOI18N // [PENDING] icon tag /** Tag for a section. Identifies that the entry is specially treated somehow. */ public static final Attributes.Name TAG_SECTION_CLASS = new Attributes.Name ("OpenIDE-Module-Class"); // NOI18N /** "Action" module section. */ // NOI18N public static final String SECTION_ACTION = "Action"; // NOI18N /** "Option" module section. */ // NOI18N public static final String SECTION_OPTION = "Option"; // NOI18N /** "Loader" module section. */ // NOI18N public static final String SECTION_LOADER = "Loader"; // NOI18N /** Option to install this loader before another. */ public static final Attributes.Name TAG_INSTALL_BEFORE = new Attributes.Name ("Install-Before"); // NOI18N /** Option to install this loader after another. */ public static final Attributes.Name TAG_INSTALL_AFTER = new Attributes.Name ("Install-After"); // NOI18N /** "Filesystem" module section. */ // NOI18N public static final String SECTION_FILESYSTEM = "Filesystem"; // NOI18N /** Display name of a file system type. Used e.g. in popup menus to add a new instance to the Repository. */ public static final Attributes.Name TAG_FILESYSTEM_NAME = new Attributes.Name ("Display-Name"); // NOI18N /** Help resource for a file system. */ public static final Attributes.Name TAG_FILESYSTEM_HELP = new Attributes.Name ("Help"); // NOI18N /** "Service" module section. */ // NOI18N public static final String SECTION_SERVICE = "Service"; // NOI18N // UNUSED-- /** Display name of an service. public static final Attributes.Name TAG_SERVICE_NAME = new Attributes.Name ("Display-Name"); */ /** Whether this service should be the default for its category. */ public static final Attributes.Name TAG_SERVICE_DEFAULT = new Attributes.Name ("Default"); // NOI18N /** "Debugger" module section. */ // NOI18N public static final String SECTION_DEBUGGER = "Debugger"; // NOI18N /** "Node" module section. */ // NOI18N public static final String SECTION_NODE = "Node"; // NOI18N /** Option to distingiush between different types of nodes. * <p>Currently the possible types are <code>Environment</code>, <code>Roots</code>, * and <code>Session</code>. If the attribute * is missing, <code>Environment</code> is assumed. */ public static final Attributes.Name TAG_NODE_TYPE = new Attributes.Name ("Type"); // NOI18N /** "ClipboardConvertor" module section. */ // NOI18N public static final String SECTION_CLIPBOARD_CONVERTOR = "ClipboardConvertor"; // NOI18N // ----------------------------------------------------------------------------- // private variables /** (display) name of module */ private String moduleName; // code name of module private String codeName; /** main class instance class */ private String mainClass; /** main class instance */ private ModuleInstall main; /** specification vesion */ private String specVersion; /** implementation version */ private String implVersion; // Dependencies of various sorts. Elements are of type Dependency. private Set dependenciesSet; /** resource for description */ private String description; /** list of sections in the module */ private ManifestSection[] sections; private static ResourceBundle bundle; // ----------------------------------------------------------------------------- // Constructor /** Create new description from a provided manifest file. * It is assumed that the JAR file is already known to the class loader. * @param name name of the JAR file * @param man the manifest file within that JAR * @exception IllegalModuleException if there is an error reading the description */ public ModuleDescription (String name, Manifest man) throws IllegalModuleException { createDescription (name, man); if (VERBOSE) { System.err.println ("Making module description for " + this); Enumeration en = dependencies (); while (en.hasMoreElements ()) System.err.println ("\t=>" + (Dependency) en.nextElement ()); } } /** Create new description from a fixed JAR file on disk (for testing). * Note that getting the actual install or check classes will not generally work * unless the JAR is already in the class path. * This constructor may be conveniently used to test parsing of a module JAR, * as well as testing cross-dependencies and so on. * @param jar the JAR file * @deprecated Only for testing. * @throws IllegalModuleException if there is an error reading the description * @throws IOException if the JAR file could not be opened or read */ public ModuleDescription (File jar) throws IllegalModuleException, IOException { this (jar.getPath (), new Manifest (new FileInputStream (jar))); } /** Create new description from a string (for testing). * This constructor may be conveniently used to test parsing of a module JAR, * as well as testing cross-dependencies and so on. * @param text the full text of the manifest file * @deprecated Only for testing. * @throws IllegalModuleException if there is an error reading the description * @throws IOException should not be thrown */ public ModuleDescription (String text) throws IllegalModuleException, IOException { this ("/no/path/to/testManifest.mf", new Manifest (new ByteArrayInputStream (text.getBytes ()))); // NOI18N } // ----------------------------------------------------------------------------- // Public interface /** Get display name of the module. * @return the name * @see #TAG_NAME */ public String getName () { return moduleName; } /** Get code name of the module. * @return the code name (should not normally change between releases except to indicate incompatible changes) * @see #TAG_MAGIC */ public String getCodeName () { return codeName; } /** Get code name base of the module. * E.g. for the code name <code>foo/3</code>, this would give <code>foo</code>. * @return the code name base (should not change between releases) * @see #getCodeName */ public String getCodeNameBase () { int slash = codeName.indexOf ("/"); // NOI18N if (slash == -1) return codeName; else return codeName.substring (0, slash); } /** Get the major release number of the module code name. * E.g. for the code name <code>foo/3</code>, this would give <code>3</code>. * @return the release number (should change between releases to indicate incompatible changes), or <code>-1</code> if unspecified * @see #getCodeName */ public int getCodeNameRelease () { int slash = codeName.indexOf ("/"); // NOI18N if (slash == -1) return -1; else return Integer.parseInt (codeName.substring (slash + 1)); } /** Get the main hook object of the module (to run hooks from). * If the module did not specify a main class, a dummy will be returned instead. * * @return main object of the module * @see #TAG_MAIN */ public synchronized ModuleInstall getModule () { try { if (mainClass != null) { // [PENDING] systemClassLoader() maybe? --jglick // Pls. don't it would make impossible to load modules from // repository in the test mode. - Petr Hrebejk main = (ModuleInstall)Beans.instantiate (TopManager.getDefault ().currentClassLoader (), mainClass); } // do not try anymore mainClass = null; } catch (Exception ex) { TopManager.getDefault ().notifyException (ex); } if (main == null) { main = MODULE_NONE; } return main; } /** Get a URL to a page describing the module. * @return the URL of a JavaHelp HelpSet file, or * <code>null</code> if the module did not specify a description * @see #TAG_DESCRIPTION * @exception IllegalStateException if the tag is specified but the HelpSet file was not found */ public URL getDescription () { // Please do not cache result--need to retry for ModuleItem to look elsewhere for help // if not found at first. (Will change currentClassLoader.) if (description == null) return null; // tries to find localized version of HelpSet (hs) file // again, do not change to systemClassLoader lest test-mode break try { return NbBundle.getLocalizedFile (description, "hs", Locale.getDefault (), TopManager.getDefault ().currentClassLoader ()); // NOI18N } catch (MissingResourceException mre) { throw new IllegalStateException (getStringFormatted ("EXC_NoHelpSetFile", description)); // NOI18N } } /** Iterates over all entries found in the module. It sends all the entries * to the provided iterator's callback methods. * * @param it iterator over all sections */ public synchronized void forEachSection (ManifestSection.Iterator it) { int s = sections.length; List al = new LinkedList (); for (int i = 0; i < s; i++) { try { sections[i].invokeIterator (it); al.add (sections[i]); } catch (Exception ex) { TopManager.getDefault().notifyException(ex); // extract the section } } if (sections.length > al.size ()) sections = (ManifestSection[]) al.toArray (new ManifestSection[al.size ()]); } /** Get all dependencies. * @return an enumeration of {@link ModuleDescription.Dependency}s * @see #getDependencies */ public Enumeration dependencies () { return Collections.enumeration (dependenciesSet); } /** Get a list of all dependencies. * @return the dependencies * @see #dependencies */ public Dependency[] getDependencies () { return (Dependency[]) dependenciesSet.toArray (new Dependency[0]); } /** Get the specification version of this module. * @return the spec version, or <code>null</code> * @see #TAG_SPEC_VERSION */ public String getSpecVersion () { return specVersion; } /** Get the implementation version of this module. * @return the impl version, or <code>null</code> * @see #TAG_IMPL_VERSION */ public String getImplVersion () { return implVersion; } /** Check whether this description satisfies all of its dependencies, and if not say why. * @param otherModules other modules which this module might require * @return <code>null</code> if satisfied, else text explaining why it was not * @see ModuleDescription.Dependency#checkForMiss */ public String reasonWhyUnsatisfied (ModuleDescription[] otherModules) throws IllegalModuleException { if (VERBOSE) System.err.println ("Checking all dependencies for " + this); String reason = null; Iterator it = dependenciesSet.iterator (); while (it.hasNext ()) { Dependency dep = (Dependency) it.next (); String miss = dep.checkForMiss (otherModules); if (miss != null) { // String text = getStringFormatted ("MSG_Why_Dep_Failed", getName (), getCodeName (), dep.toString (), miss); // NOI18N if (reason == null) reason = getStringFormatted ("MSG_Why_Dep_Failed", getName (), getCodeName () ) + miss; // NOI18N //reason = text; else reason += "\n" + miss; // NOI18N } } return reason; } /** Check whether this description depends on another module. * This does <em>not</em> check whether the dependency is fully satisfied (i.e. the versions match); * it only checks whether there is some sort of dependency or not. * Also note that implicit dependencies within sections, e.g. the implicit dependency one module * may have on another based on loader pool installation or whatnot, is not considered at all. * Modules are considered to depend on themselves. * @param other the other module to compare to * @return <code>true</code> if this module states that it depends on the other in its dependency list, <code>false</code> if it makes no such statement * @see #TAG_MODULE_DEPENDENCIES */ public boolean dependsOnModule (ModuleDescription other) { if (this == other || codeName.equals (other.codeName)) return true; Iterator it = dependenciesSet.iterator (); while (it.hasNext ()) { Dependency dep = (Dependency) it.next(); if (dep.getType () == Dependency.TYPE_MODULE && dep.getName ().equals (other.getCodeName ())) return true; } return false; } public String toString () { return getStringFormatted ("DBG_Module_ToString", getCodeName (), getName ()); // NOI18N } /** Resolve the proper ordering of a set of modules. * Checks the dependencies among the modules and attempts to order them * according to a topological sort based on cross-dependencies. * Where the ordering is not otherwise specified, orders modules alphabetically based on code name. * @param modules a set of <code>ModuleDescription</code>s to be installed * @return a list of the same <code>ModuleDescription</code>s in the order in which they should be installed * @throws IllegalModuleException if the ordering cannot be resolved (for example, due to a cyclic dependency) * @see #dependsOnModule */ public static List resolveOrdering (Set modules) throws IllegalModuleException { if (VERBOSE) { System.err.println ("Incoming module list: "); Iterator it0 = modules.iterator (); while (it0.hasNext ()) System.err.println ("\t" + (ModuleDescription) it0.next ()); } // Not a Knuth-quality topological sort here by any means! Oh well. // Predetermine all cross-dependencies (for speed). // This is a map from MD to set of MD's it depends on (not incl. itself). Map crossdeps = new HashMap (); // Map<ModuleDescription, Set<ModuleDescription>> Iterator it1 = modules.iterator (); while (it1.hasNext ()) { ModuleDescription md = (ModuleDescription) it1.next (); Set thisdeps = new HashSet (); // Set<ModuleDescription> Iterator it2 = modules.iterator (); while (it2.hasNext ()) { ModuleDescription other = (ModuleDescription) it2.next (); if (md == other) continue; if (md.dependsOnModule (other)) thisdeps.add (other); } crossdeps.put (md, thisdeps); } // Order the modules alphabetically into a new set. SortedSet sorted = new TreeSet (codeNameComparator); // SortedSet<ModuleDescription> sorted.addAll (modules); if (sorted.size () != modules.size ()) throwOverlapException (modules, sorted); // The result list. List result = new ArrayList (); // List<ModuleDescription> // Iteratively look for modules with no remaining dependencies. // We will remove items from sorted as we go and add them to result. pullin_em_out: while (sorted.size () > 0) { // System.err.println ("Sorted set size at " + sorted.size ()); Iterator it3 = sorted.iterator (); while (it3.hasNext ()) { ModuleDescription test = (ModuleDescription) it3.next (); Set remainingDeps = (Set) crossdeps.get (test); if (remainingDeps.size () == 0) { // System.err.println ("Removing " + test.getName () + " (leaf count at " + leafCount + ")"); result.add (test); it3.remove (); // Kill deps on this from other modules. Iterator it4 = sorted.iterator (); while (it4.hasNext ()) { ModuleDescription othermod = (ModuleDescription) it4.next (); if (othermod == test) throw new IllegalModuleException ("Should not happen."); // NOI18N Set otherdeps = (Set) crossdeps.get (othermod); otherdeps.remove (test); } continue pullin_em_out; } } // OK, there were none pulled out on this round => cyclic dependency. // Display a list of all modules that had cyclic dependencies, sep'd by commas. Iterator it5 = sorted.iterator (); boolean first = true; StringBuffer buf = new StringBuffer (); while (it5.hasNext ()) { if (first) first = false; else buf.append ("; "); // NOI18N ModuleDescription bad = (ModuleDescription) it5.next (); buf.append (bad.getName ()); buf.append (" (=> "); // NOI18N boolean firstagain = true; Iterator it6 = ((Set) crossdeps.get (bad)).iterator (); while (it6.hasNext ()) { if (firstagain) firstagain = false; else buf.append (", "); // NOI18N buf.append (((ModuleDescription) it6.next ()).getName ()); } buf.append (")"); // NOI18N } throw new IllegalModuleException (getStringFormatted ("EXC_Cyclic", buf.toString ())); // NOI18N } if (VERBOSE) { System.err.println ("Outgoing module list: "); Iterator hookah2 = result.iterator (); while (hookah2.hasNext ()) System.err.println ("\t" + (ModuleDescription) hookah2.next ()); } return result; } /** Throw IllegalModuleException indicating a module code name overlap. For convenience. * @param incoming raw set of modules with duplicates * @param checked sorted set of modules with duplicates removed * @throws IllegalModuleException always */ private static void throwOverlapException (Set incoming, SortedSet checked) throws IllegalModuleException { // By popular request, this message should be more helpful now: StringBuffer overlaps = new StringBuffer (); Map occurrences = new HashMap (); // Map<String,Integer> Iterator it = incoming.iterator (); while (it.hasNext ()) { ModuleDescription md = (ModuleDescription) it.next (); String cn = md.getCodeName (); Integer count = (Integer) occurrences.get (cn); if (count == null) occurrences.put (cn, new Integer (1)); else occurrences.put (cn, new Integer (count.intValue () + 1)); } boolean firstOverlap = true; Iterator it2 = occurrences.keySet ().iterator (); while (it2.hasNext ()) { String codeName = (String) it2.next (); Integer count2 = (Integer) occurrences.get (codeName); if (count2.intValue () > 1) { if (firstOverlap) firstOverlap = false; else overlaps.append (' '); overlaps.append (codeName); overlaps.append ('('); overlaps.append (count2.toString ()); overlaps.append (')'); } } throw new IllegalModuleException (getStringFormatted ("EXC_Overlapping_Code_Names", // NOI18N String.valueOf (checked.size ()), String.valueOf (incoming.size () - checked.size ()), ModuleDescription.TAG_MAGIC.toString (), overlaps.toString ())); } /** Actually generate a list of newly-installable modules in this IDE (but do not install them). * Checks all of their dependencies, and orders them properly. * If any are missing dependencies, they are removed from the set * (after notifying the user), and the remainder are reexamined in case * other modules are now missing a dependency. They are ordered in the normal fashion. * If there are exceptions in any calculations (<em>not</em> just missed dependencies), * these are propagated without any attempt at further error recovery. * @param restored a set of <code>ModuleDescription</code>s for already-installed modules which should have already been restored * @param installed a set of <code>ModuleDescription</code>s for modules which are intended for installation and may be returned * @return a (possibly empty) list of <code>ModuleDescription</code>s for modules which may be installed, in the order in which they should be installed; will be a subset of <code>installed</code> * @throws IllegalModuleException if any problem is encountered other than missed dependencies */ public static List resolveOrderingForRealInstall (Set restored, Set installed) throws IllegalModuleException { if (VERBOSE) System.err.println ("rOFRI called."); { // Check that there are not overlaps anywhere. SortedSet doubleCheck = new TreeSet (codeNameComparator); doubleCheck.addAll (restored); doubleCheck.addAll (installed); int actual = doubleCheck.size (); int presumed = restored.size () + installed.size (); if (actual != presumed) { Set combined = new HashSet (); combined.addAll (restored); combined.addAll (installed); throwOverlapException (combined, doubleCheck); } } // List of messages for modules which are *not* to be installed. List missed = new ArrayList (); // List<String> // Set of modules which will actually be installed. Set actual = new HashSet (); // Set<ModuleDescription> actual.addAll (installed); int misscount; // this time around do { misscount = 0; Iterator it = actual.iterator (); while (it.hasNext ()) { ModuleDescription test = (ModuleDescription) it.next (); Set whatCanIStillUse = new HashSet (); // Set<ModuleDescription> whatCanIStillUse.addAll (restored); whatCanIStillUse.addAll (actual); String miss = test.reasonWhyUnsatisfied ((ModuleDescription[]) whatCanIStillUse.toArray (new ModuleDescription[0])); if (miss != null) { misscount++; it.remove (); missed.add (miss); } } } while (misscount > 0); /* if (missed.size () > 0) { TopManager.getDefault ().notify (new NotifyDescriptor.Message (new Object[] { getStringFormatted ("MSG_Some_Missed", "" + missed.size (), "" + actual.size (), "" + (actual.size () + missed.size ())), (String[]) missed.toArray (new String[missed.size ()]) })); } */ return resolveOrdering (actual); } /** Check whether specification versions are compatible. * True if older version is less-than-or-equal-to newer version acc. to a Dewey-decimal lexicographic compare. * <p>This algorithm should hopefully match that used by the Java Versioning specification * (which unfortunately does not make its algorithm public). * @param older the presumed older version * @param newer the presumed newer version * @throws IllegalModuleException in case of a number format error * @see Package */ public static boolean compatibleWith (String older, String newer) throws IllegalModuleException { if (older == null || newer == null) return false; StringTokenizer oldTok = new StringTokenizer (older, "."); // NOI18N StringTokenizer newTok = new StringTokenizer (newer, "."); // NOI18N while (oldTok.hasMoreTokens () || newTok.hasMoreTokens ()) { if (! newTok.hasMoreTokens ()) // E.g. 1.2.3 vs. 1.2: return false; if (! oldTok.hasMoreTokens ()) // E.g. 1.2 vs. 1.2.3: return true; String oldElt=oldTok.nextToken (); String newElt=newTok.nextToken (); int oldNum = 0, newNum = 0; try { oldNum = Integer.parseInt (oldElt); newNum = Integer.parseInt (newElt); } catch (NumberFormatException e) { throw new IllegalModuleException (e); } if (oldNum < 0 || newNum < 0) return false; if (oldNum < newNum) // E.g. 1.2 vs. 1.3: return true; if (oldNum > newNum) // E.g. 1.3 vs. 1.2: return false; // Continue... } // Equal: return true; } // ----------------------------------------------------------------------------- // Private methods /** Check whether a possible code name is valid. */ static void checkCodeName (String codeName) throws IllegalModuleException { checkCodeName ("", codeName); // NOI18N } /** Check whether a possible code name is valid. * @param moduleName name of module that contains this code name or "" if not known * @param codeName code name of the module */ static void checkCodeName (String moduleName, String codeName) throws IllegalModuleException { String base; int slash = codeName.indexOf ("/"); // NOI18N int release; if (slash == -1) { base = codeName; release = -1; } else { base = codeName.substring (0, slash); try { release = Integer.parseInt (codeName.substring (slash + 1)); } catch (NumberFormatException e) { throw new IllegalModuleException (getStringFormatted ("EXC_Non_Numeric_Release", codeName, moduleName)); // NOI18N } } for (int ch = 0; ch < base.length (); ch++) { char c = base.charAt (ch); if (! (Character.isJavaIdentifierPart (c) || c == '.')) throw new IllegalModuleException (getStringFormatted ("EXC_Bad_Char_In_Code_Name", codeName, moduleName)); // NOI18N } } /** Check whether a possible specification version is valid. */ static void checkSpec (String spec) throws IllegalModuleException { try { if (! compatibleWith ("0", spec)) // NOI18N throw new IllegalModuleException (getStringFormatted ("EXC_Bad_Spec", spec)); // NOI18N } catch (IllegalModuleException e) { throw new IllegalModuleException (getStringFormatted ("EXC_Bad_Spec_Why", spec, e.getMessage ())); // NOI18N } } /** Creates description of the module grabbed from the jar archive manifest. * @return module description * @exception IllegalModuleException if there is error reading the description */ private void createDescription (String name, Manifest man) throws IllegalModuleException { Attributes attr = man.getMainAttributes (); // ----------------------------------------------------------------------------- // Global tags : required: TAG_MAGIC codeName = attr.getValue (TAG_MAGIC); if (codeName == null) { // not module throw new IllegalModuleException (getStringFormatted ("EXC_Not_A_Module", TAG_MAGIC.toString (), name)); // NOI18N } checkCodeName (name, codeName); moduleName = (String) NbBundle.getLocalizedValue (attr, TAG_NAME); if (moduleName == null) { String str = name; int from = str.lastIndexOf('/'); int till = str.lastIndexOf('.'); moduleName = str.substring((from == -1) ? 0 : from + 1, (till == -1) ? str.length() : till); } // ----------------------------------------------------------------------------- // Versioning tags specVersion = attr.getValue (TAG_SPEC_VERSION); if (specVersion != null) checkSpec (specVersion); implVersion = attr.getValue (TAG_IMPL_VERSION); // ----------------------------------------------------------------------------- // Dependency tags dependenciesSet = new HashSet (); parseDependencies (Dependency.TYPE_MODULE, attr.getValue (TAG_MODULE_DEPENDENCIES), dependenciesSet); parseDependencies (Dependency.TYPE_PACKAGE, attr.getValue (TAG_PACKAGE_DEPENDENCIES), dependenciesSet); parseDependencies (Dependency.TYPE_JAVA, attr.getValue (TAG_JAVA_DEPENDENCIES), dependenciesSet); parseDependencies (Dependency.TYPE_IDE, attr.getValue (TAG_IDE_DEPENDENCIES), dependenciesSet); // ----------------------------------------------------------------------------- // ModuleInstall tags mainClass = attr.getValue (TAG_MAIN); if (mainClass != null) { mainClass = createPackageName(mainClass); } // ----------------------------------------------------------------------------- // other tags description = attr.getValue (TAG_DESCRIPTION); // iterator of Attributes Iterator en = man.getEntries ().entrySet ().iterator (); ArrayList v = new ArrayList (); while (en.hasNext ()) { Map.Entry entry = (Map.Entry)en.next (); Attributes a = (Attributes) entry.getValue (); ManifestSection s = ManifestSection.createSection ((String)entry.getKey (), a); if (s != null) { // if the entry describes section class v.add (s); } } sections = new ManifestSection[v.size ()]; v.toArray (sections); } /** Parse dependencies from tags. * @param type like Dependency.type * @param body actual text of tag body; if <code>null</code>, does nothing * @param deps set of dependencies to add to * @throws IllegalModuleException if in bad format */ private static void parseDependencies (int type, String body, Set deps) throws IllegalModuleException { if (body == null) return; // First split on commas. StringTokenizer tok = new StringTokenizer (body, ","); // NOI18N if (! tok.hasMoreTokens ()) throw new IllegalModuleException (getStringFormatted ("EXC_No_Deps_Given", body)); // NOI18N while (tok.hasMoreTokens ()) { String onedep = tok.nextToken (); StringTokenizer tok2 = new StringTokenizer (onedep, " \t\n\r"); // NOI18N if (! tok2.hasMoreTokens ()) throw new IllegalModuleException (getStringFormatted ("EXC_No_Name_In_Dep", onedep)); // NOI18N String name = tok2.nextToken (); int comparison; String version; if (tok2.hasMoreTokens ()) { String compthing = tok2.nextToken (); if (compthing.equals (">")) // NOI18N comparison = Dependency.COMPARE_SPEC; else if (compthing.equals ("=")) // NOI18N comparison = Dependency.COMPARE_IMPL; else throw new IllegalModuleException (getStringFormatted ("EXC_Unrec_Comp_Str", compthing)); // NOI18N if (! tok2.hasMoreTokens ()) throw new IllegalModuleException (getStringFormatted ("EXC_Comp_Str_Without_Vers", onedep)); // NOI18N version = tok2.nextToken (); if (tok2.hasMoreTokens ()) throw new IllegalModuleException (getStringFormatted ("EXC_Garbage", onedep)); // NOI18N } else { comparison = Dependency.COMPARE_ANY; version = null; } deps.add (new Dependency (type, name, comparison, version)); } } /** Convert a class file name to a resource name suitable for Beans.instantiate. * @param name resource name of class file * @return class name without the <code>.class</code>/<code>.ser</code> extension, and using dots as package separator * @throws IllegalModuleException if the name did not have a valid extension, or originally contained dots outside the extension, etc. */ static String createPackageName(String name) throws IllegalModuleException { String clExt = ".class"; // NOI18N if (!name.endsWith(clExt)) { // try different extension clExt = ".ser"; // NOI18N } if (name.endsWith(clExt)) { String bareName = name.substring(0, name.length() - clExt.length()); if (bareName.length () == 0) // ".class" // NOI18N throw new IllegalModuleException (getStringFormatted ("EXC_Bad_Class_File_Name", name)); // NOI18N if (bareName.charAt (0) == '/') // "/foo/bar.class" // NOI18N throw new IllegalModuleException (getStringFormatted ("EXC_Bad_Class_File_Name", name)); // NOI18N if (bareName.charAt (bareName.length () - 1) == '/') // "foo/bar/.class" // NOI18N throw new IllegalModuleException (getStringFormatted ("EXC_Bad_Class_File_Name", name)); // NOI18N if (bareName.indexOf ('.') != -1) // "foo.bar.class" // NOI18N throw new IllegalModuleException (getStringFormatted ("EXC_Bad_Class_File_Name", name)); // NOI18N return bareName.replace('/', '.'); } else { // "foo/bar" or "foo.bar" // NOI18N throw new IllegalModuleException (getStringFormatted ("EXC_Bad_Class_File_Name", name)); // NOI18N } } static ResourceBundle getBundle () { if (bundle == null) bundle = NbBundle.getBundle (ModuleDescription.class); return bundle; } static String getString (String key) { return getBundle ().getString (key); } static String getStringFormatted (String key, Object[] args) { return MessageFormat.format (getString (key), args); } static String getStringFormatted (String key, String arg1) { return getStringFormatted (key, new Object[] { arg1 }); } static String getStringFormatted (String key, String arg1, String arg2) { return getStringFormatted (key, new Object[] { arg1, arg2 }); } static String getStringFormatted (String key, String arg1, String arg2, String arg3) { return getStringFormatted (key, new Object[] { arg1, arg2, arg3 }); } static String getStringFormatted (String key, String arg1, String arg2, String arg3, String arg4) { return getStringFormatted (key, new Object[] { arg1, arg2, arg3, arg4 }); } /** A type of dependency that the module can have on its environment. * @see ModuleDescription */ public static final class Dependency extends Object { /** Dependency on another module. */ public final static int TYPE_MODULE = 1; /** Dependency on a package. */ public final static int TYPE_PACKAGE = 2; /** Dependency on Java. */ public final static int TYPE_JAVA = 3; /** Dependency on the IDE. */ public final static int TYPE_IDE = 4; // Type of dependency. private int type; // Name of dependency. private String name; /** Comparison by specification version. * The actual version must equal or exceed the requested * version according to Dewey decimal numbering. * @see ModuleDescription#compatibleWith */ public final static int COMPARE_SPEC = 1; /** Comparison by implementation version. * The actual and requested versions must match exactly as strings. */ public final static int COMPARE_IMPL = 2; /** No comparison, just require the dependency to be present. */ public final static int COMPARE_ANY = 3; // Type of comparison. private int comparison; // Requested version of dependency. private String version; /** Create a new dependency object. * @param type the type of dependency (one of {@link #TYPE_MODULE}, {@link #TYPE_PACKAGE}, {@link #TYPE_JAVA}, or {@link #TYPE_IDE}) * @param name the name of the dependency, e.g. <code>com.mycom.myothermodule/2</code> or <code>Java</code> * @param comparison the type of comparison (one of {@link #COMPARE_SPEC}, {@link #COMPARE_IMPL}, or {@link #COMPARE_ANY}) * @param version the string version requested; may be <code>null</code> * @throws IllegalModuleException if there was something invalid about the parameters */ Dependency (int type, String name, int comparison, String version) throws IllegalModuleException { this.type = type; this.name = name; this.comparison = comparison; this.version = version; // Sanity. if (comparison == COMPARE_SPEC) ModuleDescription.checkSpec (version); switch (type) { case TYPE_MODULE: ModuleDescription.checkCodeName (name); break; case TYPE_PACKAGE: // Close enough: would permit a slash, but that's not a big deal really. ModuleDescription.checkCodeName (name); break; case TYPE_JAVA: if (! (name.equals ("Java") || name.equals ("VM"))) // NOI18N throw new IllegalModuleException (ModuleDescription.getStringFormatted ("EXC_Bad_Java_Dep", toString ())); // NOI18N break; case TYPE_IDE: if (! (name.equals ("IDE"))) { // NOI18N int slash = name.indexOf ("/"); // NOI18N boolean ok; if (slash == -1) { ok = false; } else { try { Integer.parseInt (name.substring (slash + 1)); ok = true; } catch (NumberFormatException e) { ok = false; } } if (! ok) throw new IllegalModuleException (ModuleDescription.getStringFormatted ("EXC_Bad_IDE_Dep", toString ())); // NOI18N } if (comparison == COMPARE_ANY) throw new IllegalModuleException (ModuleDescription.getStringFormatted ("EXC_IDE_Dep_Uncompared", toString ())); // NOI18N break; default: throw new IllegalModuleException ("unknown type"); // NOI18N } } /** Get the type. * @return the type */ public int getType () { return type; } /** Get the name. * @return the name */ public String getName () { return name; } /** Get the comparison type. * @return the comparison type */ public int getComparison () { return comparison; } /** Get the version. * @return the version (may be <code>null</code>) */ public String getVersion () { return version; } /** Check whether this dependency is currently satisfied by the supplied parameters. * Note that for dependencies of type {@link #TYPE_PACKAGE}, the check is against whether * that package is loaded into the classloader used by the invoking class. * <p>The following system properties, with sample values, are used to check dependencies * of type {@link #TYPE_IDE}: * <table border=1><tr><th>Name</th><th>Description</th><th>Sample</th></tr> * <tr><td><code>org.openide.specification.version<code></td><td>Specification version</td><td><code>1.0.12</code></td></tr> * <tr><td><code>org.openide.version<code></td><td>Implementation version</td><td><code>build #999</code></td></tr> * <tr><td><code>org.openide.major.version<code></td><td>"Code name", i.e. IDE incompatible release</td><td><code>IDE/2</code></td></tr> * </table> * @param otherModules other modules which this dependency might require * @return <code>null</code> if satisfied, else a message explaining why it was not satisfied */ public String checkForMiss (ModuleDescription[] otherModules) throws IllegalModuleException { switch (type) { case TYPE_MODULE: for (int i = 0; i < otherModules.length; i++) { ModuleDescription other = otherModules[i]; if (name.equals (other.getCodeName ())) { if (comparison == COMPARE_ANY) { return null; } else if (comparison == COMPARE_SPEC) { if (other.getSpecVersion () == null) return ModuleDescription.getStringFormatted ("MSG_Module_Spec_None", other.getName ()); // NOI18N else if (! ModuleDescription.compatibleWith (version, other.getSpecVersion ())) return ModuleDescription.getStringFormatted ("MSG_Module_Spec_Bad", other.getName (), other.getSpecVersion (), version); // NOI18N else return null; } else { // COMPARE_IMPL if (other.getImplVersion () == null) return ModuleDescription.getStringFormatted ("MSG_Module_Impl_None", other.getName ()); // NOI18N else if (! other.getImplVersion ().equals (version)) return ModuleDescription.getStringFormatted ("MSG_Module_Impl_Bad", other.getName (), other.getImplVersion (), version); // NOI18N else return null; } } } return ModuleDescription.getStringFormatted ("MSG_Module_None", name); // NOI18N case TYPE_PACKAGE: Package pkg = Package.getPackage (name); if (pkg == null) return ModuleDescription.getStringFormatted ("MSG_Package_None", name); // NOI18N if (comparison == COMPARE_ANY) { return null; } else if (comparison == COMPARE_SPEC) { if (pkg.getSpecificationVersion () == null) return ModuleDescription.getStringFormatted ("MSG_Package_Spec_None", pkg.getName ()); // NOI18N else if (! ModuleDescription.compatibleWith (version, pkg.getSpecificationVersion ())) return ModuleDescription.getStringFormatted ("MSG_Package_Spec_Bad", pkg.getName (), pkg.getSpecificationVersion (), version); // NOI18N else return null; } else { // COMPARE_IMPL if (pkg.getImplementationVersion () == null) return ModuleDescription.getStringFormatted ("MSG_Package_Impl_None", pkg.getName ()); // NOI18N else if (! pkg.getImplementationVersion ().equals (version)) return ModuleDescription.getStringFormatted ("MSG_Package_Impl_Bad", pkg.getName (), pkg.getImplementationVersion (), version); // NOI18N else return null; } case TYPE_JAVA: // Assume all versions are set, and that COMPARE_ANY was not used. if (name.equals ("Java")) { // NOI18N if (comparison == COMPARE_SPEC) { return ModuleDescription.compatibleWith (version, System.getProperty ("java.specification.version")) ? null : ModuleDescription.getStringFormatted ("MSG_Java_Spec", version); // NOI18N } else { // COMPARE_IMPL return System.getProperty ("java.version").equals (version) ? null : ModuleDescription.getStringFormatted ("MSG_Java_Impl", version); // NOI18N } } else { // VM if (comparison == COMPARE_SPEC) { return ModuleDescription.compatibleWith (version, System.getProperty ("java.vm.specification.version")) ? null : ModuleDescription.getStringFormatted ("MSG_VM_Spec", version); // NOI18N } else { // COMPARE_IMPL return System.getProperty ("java.vm.version").equals (version) ? null : ModuleDescription.getStringFormatted ("MSG_VM_Impl", version); // NOI18N } } case TYPE_IDE: String IDEName = System.getProperty ("org.openide.major.version", "IDE"); String IDESpecVersion = System.getProperty ("org.openide.specification.version", "0.0"); String IDEImplVersion = System.getProperty ("org.openide.version", "<unknown>"); if (! IDEName.equals (name)) return ModuleDescription.getStringFormatted ("MSG_IDE_Name", name, IDEName); // NOI18N if (comparison == COMPARE_SPEC) { return ModuleDescription.compatibleWith (version, IDESpecVersion) ? null : ModuleDescription.getStringFormatted ("MSG_IDE_Spec", version, IDESpecVersion); // NOI18N } else if (comparison == COMPARE_IMPL) { return version.equals (IDEImplVersion) ? null : ModuleDescription.getStringFormatted ("MSG_IDE_Impl", version, IDEImplVersion); // NOI18N } else { // COMPARE_ANY return null; } } throw new IllegalModuleException ("should never happen"); // NOI18N } public boolean equals (Object o) { if (o == null || ! (o instanceof Dependency)) return false; Dependency oo = (Dependency) o; return type == oo.type && name.equals (oo.name) && comparison == oo.comparison && ((version == null && oo.version == null) || (version != null && oo.version != null && version.equals (oo.version))); } public int hashCode () { // Jesse's super-scientific technique: return (type * 57) ^ name.hashCode () ^ (comparison * 231) ^ (version == null ? 111 : version.hashCode ()); } public String toString () { // Too late at night to bother with ChoiceFormat. return ModuleDescription.getStringFormatted ("DBG_Dependency_ToString", // NOI18N (type == TYPE_MODULE ? "Module" : // NOI18N type == TYPE_PACKAGE ? "Package" : // NOI18N type == TYPE_JAVA ? "Java" : // NOI18N type == TYPE_IDE ? "IDE" : "???"), // NOI18N name, comparison == COMPARE_ANY ? "" : // NOI18N ModuleDescription.getStringFormatted ("DBG_Dependency_Comparison", // NOI18N (comparison == COMPARE_SPEC ? ">" : // NOI18N comparison == COMPARE_IMPL ? "=" : "???"), // NOI18N version)); } } /** Empty instance for used if the module is not present in the jar file. * Note that default ModuleInstall instance does nothing, so that is fine. */ private final static ModuleInstall MODULE_NONE = new ModuleInstall (); } /* * Log * 39 Gandalf 1.38 1/13/00 Ian Formanek NOI18N * 38 Gandalf 1.37 1/12/00 Ian Formanek NOI18N * 37 Gandalf 1.36 1/6/00 Martin Balin Removed final on some * fields to make build process happy * 36 Gandalf 1.35 1/6/00 Petr Hrebejk Messages about * unsatisfied dependencies modified * 35 Gandalf 1.34 11/6/99 Jesse Glick Bugfix: getDescription * was passing on MissingResourceException rather than throwing * IllegalStateException. * 34 Gandalf 1.33 10/27/99 Petr Hrebejk Testing of modules added * 33 Gandalf 1.32 10/22/99 Ian Formanek NO SEMANTIC CHANGE - Sun * Microsystems Copyright in File Comment * 32 Gandalf 1.31 10/8/99 Jesse Glick Bugfix: * NullPointerException after one or more sections failed their iterator, * and a second iterator was run. * 31 Gandalf 1.30 10/4/99 Jesse Glick Removed unused service * type name. * 30 Gandalf 1.29 9/30/99 Jesse Glick #3713 & #1716--module * install messaging cleanup. * 29 Gandalf 1.28 9/13/99 Jesse Glick [JavaDoc], and removed * long-obsolete TAG_TOOLBAR/ TAG_MENU/ TAG_KEY from the actions area. * 28 Gandalf 1.27 9/10/99 Jaroslav Tulach Service section. * 27 Gandalf 1.26 8/27/99 Jesse Glick Fixed #3590. Cleaned up * random stuff in ManifestSection. Removed deprecated call from * ModuleDescription test constructor. * 26 Gandalf 1.25 8/9/99 Petr Hrebejk Update-Loacation tag end * method removed from Open API * 25 Gandalf 1.24 6/29/99 Jesse Glick [JavaDoc] * 24 Gandalf 1.23 6/9/99 Ian Formanek manifest tags changed to * NetBeans- * 23 Gandalf 1.22 6/8/99 Ian Formanek ---- Package Change To * org.openide ---- * 22 Gandalf 1.21 6/2/99 Jesse Glick Modules may specify an * update location. * 21 Gandalf 1.20 5/27/99 Jesse Glick Bye-bye InstallCheck! * 20 Gandalf 1.19 5/27/99 Jesse Glick [JavaDoc] * 19 Gandalf 1.18 5/27/99 Jaroslav Tulach Executors rearanged. * 18 Gandalf 1.17 5/10/99 Jesse Glick Module versioning--IDE * version numbers refined, made into system properties. * 17 Gandalf 1.16 5/10/99 Jesse Glick [JavaDoc] * 16 Gandalf 1.15 5/7/99 Jaroslav Tulach Help. * 15 Gandalf 1.14 5/7/99 Jesse Glick Module localization. * 14 Gandalf 1.13 5/5/99 Jaroslav Tulach Gives name of module with * wrong version on startup. * 13 Gandalf 1.12 5/4/99 Jesse Glick More useful messages * after failed dep check. * 12 Gandalf 1.11 5/4/99 Jesse Glick Debug verbosity, OFF BY * DEFAULT. Use -Dcom.netbeans.ide.modules.ModuleDescription.VERBOSE=true * to enable. * 11 Gandalf 1.10 4/28/99 Jesse Glick More thorough error * checking. Also, resolveOrdering generates an order which is as close as * possible to the alphabetical order, given the dependencies--which is * what it should do, I think, according to the documentation I gave it. * 10 Gandalf 1.9 4/28/99 Jesse Glick Added * resolveOrderingForRealInstall. getModule & getCheck notify exceptions. * 9 Gandalf 1.8 4/28/99 Jesse Glick resolveOrdering more * informative about errors. Dependency onstructor package-private. * 8 Gandalf 1.7 4/28/99 Jaroslav Tulach Version back. Sorry. * 7 Gandalf 1.6 4/28/99 Jaroslav Tulach resolveOrdring takes * Collection, Set is not necessary * 6 Gandalf 1.5 4/28/99 Jesse Glick [JavaDoc] * 5 Gandalf 1.4 4/28/99 Jesse Glick Lots of localizations, * and a bit more JavaDoc. * 4 Gandalf 1.3 4/28/99 Jesse Glick Implementation of real * module versioning. * 3 Gandalf 1.2 4/7/99 Ian Formanek Rename * Section->ManifestSection * 2 Gandalf 1.1 4/7/99 Ian Formanek Added * versioning/dependency stuff * 1 Gandalf 1.0 4/7/99 Ian Formanek * $ */